﻿using Newtonsoft.Json;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace System
{
    /// <summary>
    /// 方法调用监控器
    /// </summary>
    internal class CallerMonitor
    {
        public string MemberName { get; set; }
        /// <summary>
        /// 源文件路径
        /// </summary>
        public string SourceFilePath { get; set; }
        /// <summary>
        /// 代码执行起始行号
        /// </summary>
        public int SourceLineNumber { get; set; }

        public string FullName { get; set; }

        /// <summary>
        /// 开始监控时间
        /// </summary>
        public DateTime? StartTime { get; private set; }
        /// <summary>
        /// 结束监控时间
        /// </summary>
        public DateTime? EndTime { get; private set; }

        private int _threadID;
        /// <summary>
        /// 线程ID
        /// </summary>
        public int ThreadID { get => _threadID; }

        private double _timeConsuming = -1;
        /// <summary>
        /// 执行耗时
        /// </summary>
        public double TimeConsuming
        {
            get
            {
                if (StartTime != null && EndTime != null)
                {
                    _timeConsuming = (EndTime - StartTime).Value.TotalMilliseconds;
                }
                return _timeConsuming;
            }
        }


        static volatile int counter = 0;
        static volatile int analyzeCount = 0;

        /// <summary>
        /// 监控记录
        /// </summary>
        public static ConcurrentDictionary<string, List<CallerMonitor>> Records { get; set; } = new ConcurrentDictionary<string, List<CallerMonitor>>();

        /// <summary>
        /// 创建监控实例,并获取堆栈调用信息
        /// </summary>
        /// <param name="memberName"></param>
        /// <param name="sourceFilePath"></param>
        /// <param name="sourceLineNumber"></param>
        public CallerMonitor(
            string memberName = default,
            string sourceFilePath = default,
            int sourceLineNumber = default(int)
            )
        {
            this._threadID = Thread.CurrentThread.ManagedThreadId;
            if (counter++ % 100000 == 0 && Records.Count > 1)
            {
                Task.Factory.StartNew(() =>
                {
                    AnalyzeRecords();
                });
            }
            var stackTrack = new StackTrace(true);
            var method = stackTrack.GetFrame(1).GetMethod();
            this.FullName = $"{method?.DeclaringType?.FullName}.{(method.Name == ".ctor" ? method?.DeclaringType?.Name : method.Name)}";
            this.MemberName = memberName;
            this.SourceFilePath = sourceFilePath;
            this.SourceLineNumber = sourceLineNumber;
        }

        /// <summary>
        /// 开始监控
        /// </summary>
        /// <returns></returns>
        public CallerMonitor Start()
        {
            this.StartTime = DateTime.Now;

            var currentArray = Records.GetOrAdd($"{MemberName} {SourceLineNumber} {SourceFilePath}", _ => new List<CallerMonitor>());
            lock (currentArray)
            {
                currentArray.Add(this);
            }

            return this;
        }

        /// <summary>
        /// 结束监控
        /// </summary>
        /// <returns></returns>
        public CallerMonitor Stop()
        {
            this.EndTime = DateTime.Now;
            return this;
        }

        /// <summary>
        /// 调用堆栈分析报告
        /// </summary>
        public static void AnalyzeRecords()
        {
            analyzeCount = analyzeCount++;
            string json = JsonConvert.SerializeObject(Records);
            var copy = JsonConvert.DeserializeObject<Dictionary<string, List<CallerMonitor>>>(json);

            var dir = Path.Combine(DateTime.Now.ToString("yyyy-mm-dd"), analyzeCount.ToString());

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            File.WriteAllText(Path.Combine(dir, "Records.json"), json);

            Task.Run(() =>
            {
                DataTable dt = new DataTable();
                dt.Columns.Add("FullName");
                dt.Columns.Add("StartTime");
                dt.Columns.Add("EndTime");
                dt.Columns.Add("TimeConsuming");

                var result1 = copy.Select(p => p.Value)
                    .SelectMany(p => p)
                    .OrderBy(p => p.StartTime);

                foreach (var item in result1)
                {
                    var newRow = dt.NewRow();
                    newRow[0] = item.FullName;
                    newRow[1] = item.StartTime.Value.ToString("HH:mm:ss fff");
                    newRow[2] = item.EndTime;
                    newRow[3] = item.TimeConsuming;
                }

                NPOIHelper.DataTableToExcel(dt, Path.Combine(dir, "report1.xsl"));
            });


            Task.Run(() =>
            {
                DataTable dt = new DataTable();
                dt.Columns.Add("FullName");
                dt.Columns.Add("StartTime");
                dt.Columns.Add("EndTime");
                dt.Columns.Add("TotalConsumingTime");
                dt.Columns.Add("ExcuteCount");

                var result1 = from item in copy
                              let asc = item.Value.OrderBy(p => p.StartTime)
                              let first = asc.FirstOrDefault()
                              let last = asc.LastOrDefault()
                              let lastTime = asc.LastOrDefault(p => p.EndTime != null)?.EndTime
                              select new
                              {
                                  FullName = item.Key,
                                  TotalConsumingTime = lastTime != null ? (lastTime.Value - first.StartTime.Value).TotalMilliseconds : -1,
                                  StartTime = first.StartTime.Value,
                                  EndTime = lastTime,
                                  ExcuteCount = item.Value.Count
                              };


                foreach (var item in result1)
                {
                    var newRow = dt.NewRow();
                    newRow[0] = item.FullName;
                    newRow[1] = item.StartTime;
                    newRow[2] = item.EndTime;
                    newRow[3] = item.TotalConsumingTime;
                    newRow[4] = item.ExcuteCount;
                }

                NPOIHelper.DataTableToExcel(dt, Path.Combine(dir, "report2.xsl"));
            });
        }
    }
}
